use bytes::{buf::UninitSlice, BufMut};

use super::from_hex_digit;

#[allow(dead_code)]
pub enum EscapeType {
    Uri,
    Args,
    UriComponent,
}

// 额外对如下常见字符进行转换 '?' '%' '#' ' '
const URI_ESCAPE_TAB: [u32; 8] = [
    0xffffffff, /* 1111 1111 1111 1111  1111 1111 1111 1111 */
    /* ?>=< ;:98 7654 3210  /.-, +*)( '&%$ #"!  */
    /* 1000 0000 0000 0000  0000 0000 0010 1001 */
    0x80000029, /* 1000 0000 0000 0000  0000 0000 0010 1001 */
    /* _^]\ [ZYX WVUT SRQP  ONML KJIH GFED CBA@ */
    /* 0000 0000 0000 0000  0000 0000 0000 0000 */
    0x00000000, /* 0000 0000 0000 0000  0000 0000 0000 0000 */
    /*  ~}| {zyx wvut srqp  onml kjih gfed cba` */
    /* 1000 0000 0000 0000  0000 0000 0000 0000 */
    0x80000000, /* 1000 0000 0000 0000  0000 0000 0000 0000 */
    0xffffffff, /* 1111 1111 1111 1111  1111 1111 1111 1111 */
    0xffffffff, /* 1111 1111 1111 1111  1111 1111 1111 1111 */
    0xffffffff, /* 1111 1111 1111 1111  1111 1111 1111 1111 */
    0xffffffff, /* 1111 1111 1111 1111  1111 1111 1111 1111 */
];

// 额外对如下常见字符串进行转换 '?' '+' '%' '#' ' '
const ARGS_ESCAPE_TAB: [u32; 8] = [
    0xffffffff, /* 1111 1111 1111 1111  1111 1111 1111 1111 */
    /* ?>=< ;:98 7654 3210  /.-, +*)( '&%$ #"!  */
    /* 1000 0000 0000 0000  0000 1000 0010 1001 */
    0x80000829, /* 1000 0000 0000 0000  0000 1000 0010 1001 */
    /* _^]\ [ZYX WVUT SRQP  ONML KJIH GFED CBA@ */
    /* 0000 0000 0000 0000  0000 0000 0000 0000 */
    0x00000000, /* 0000 0000 0000 0000  0000 0000 0000 0000 */
    /*  ~}| {zyx wvut srqp  onml kjih gfed cba` */
    /* 1000 0000 0000 0000  0000 0000 0000 0000 */
    0x80000000, /* 1000 0000 0000 0000  0000 0000 0000 0000 */
    0xffffffff, /* 1111 1111 1111 1111  1111 1111 1111 1111 */
    0xffffffff, /* 1111 1111 1111 1111  1111 1111 1111 1111 */
    0xffffffff, /* 1111 1111 1111 1111  1111 1111 1111 1111 */
    0xffffffff, /* 1111 1111 1111 1111  1111 1111 1111 1111 */
];

// 额外对如下常见字符串进行转换
// '?' '>' '=' '<' ';' ':' '/' ',' '+' '&' '%' '$' '#' '"' ' '
// '^' ']' '\' '[' '@'
// ' ' '}' '|' '{' '`'
static URL_COMPONENT_ESCAPE_TAB: [u32; 8] = [
    0xffffffff, /* 1111 1111 1111 1111  1111 1111 1111 1111 */
    /* ?>=< ;:98 7654 3210  /.-, +*)( '&%$ #"!  */
    /* 1111 1100 0000 0000  1001 1000 0111 1101 */
    0xfc00987d, /* 1111 1100 0000 0000  1001 1000 0111 1101 */
    /* _^]\ [ZYX WVUT SRQP  ONML KJIH GFED CBA@ */
    /* 0111 1000 0000 0000  0000 0000 0000 0001 */
    0x78000001, /* 0111 1000 0000 0000  0000 0000 0000 0001 */
    /*  ~}| {zyx wvut srqp  onml kjih gfed cba` */
    /* 1011 1000 0000 0000  0000 0000 0000 0001 */
    0xb8000001, /* 1011 1000 0000 0000  0000 0000 0000 0001 */
    0xffffffff, /* 1111 1111 1111 1111  1111 1111 1111 1111 */
    0xffffffff, /* 1111 1111 1111 1111  1111 1111 1111 1111 */
    0xffffffff, /* 1111 1111 1111 1111  1111 1111 1111 1111 */
    0xffffffff, /* 1111 1111 1111 1111  1111 1111 1111 1111 */
];

static HEX_UPPER: [u8; 16] = [
    b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'A', b'B',
    b'C', b'D', b'E', b'F',
];

#[inline]
fn url_encode_inner(
    src: &[u8],
    dst_chunk: &mut UninitSlice,
    escape_tab: &[u32; 8],
) -> usize {
    let dst_ptr = dst_chunk.as_mut_ptr();
    let mut i: usize = 0;

    for v in src {
        if (escape_tab[(*v >> 5) as usize] & (1 << (*v & 0x1f))) != 0 {
            unsafe {
                *(dst_ptr.add(i)) = b'%';
                *(dst_ptr.add(i + 1)) = HEX_UPPER[(*v >> 4) as usize];
                *(dst_ptr.add(i + 2)) = HEX_UPPER[(*v & 0xf) as usize];
            }

            i += 3;
            continue;
        }

        unsafe {
            *(dst_ptr.add(i)) = *v;
        }
        i += 1;
    }

    i
}

// 建议dst预留空间是src长度的3倍以上.
#[inline]
#[allow(dead_code)]
pub fn url_encode<B>(src: &[u8], dst: &mut B, escape_type: EscapeType) -> usize
where
    B: BufMut + ?Sized,
{
    let escape_tab = match escape_type {
        EscapeType::Uri => &URI_ESCAPE_TAB,
        EscapeType::Args => &ARGS_ESCAPE_TAB,
        EscapeType::UriComponent => &URL_COMPONENT_ESCAPE_TAB,
    };

    let dst_chunk = dst.chunk_mut();
    if dst_chunk.len() >= src.len() * 3 {
        let size = url_encode_inner(src, dst_chunk, escape_tab);
        unsafe {
            dst.advance_mut(size);
        }
        return size;
    }

    let mut size = 0;

    for v in src {
        if (escape_tab[(*v >> 5) as usize] & (1 << (*v & 0x1f))) != 0 {
            dst.put_slice(&[
                b'%',
                HEX_UPPER[(*v >> 4) as usize],
                HEX_UPPER[(*v & 0xf) as usize],
            ]);
            size += 3;
            continue;
        }

        dst.put_bytes(*v, 1);
        size += 1;
    }

    size
}

// 建议
fn url_decode_inner(src: &[u8], dst_chunk: &mut UninitSlice) -> usize {
    let mut idx = 0;
    let mut size = 0;

    let src_len = src.len();
    let dst_ptr = dst_chunk.as_mut_ptr();

    loop {
        if idx == src_len {
            break;
        }

        let ch = src[idx];
        if ch == b'%' && idx + 2 < src_len {
            let d1 = from_hex_digit(src[idx + 1]);
            let d2 = from_hex_digit(src[idx + 2]);
            if d1.is_none() || d2.is_none() {
                unsafe {
                    *(dst_ptr.add(size)) = b'%';
                    *(dst_ptr.add(size + 1)) = d1.unwrap();
                    *(dst_ptr.add(size + 2)) = d2.unwrap();
                };
                size += 3;
                idx += 3;
                continue;
            }

            unsafe { *(dst_ptr.add(size)) = (d1.unwrap() << 4) | d2.unwrap() }
            size += 1;
            idx += 3;
            continue;
        }

        unsafe {
            *(dst_ptr.add(size)) = ch;
        }
        idx += 1;
        size += 1;
    }

    size
}

#[allow(dead_code)]
pub fn url_decode<B>(src: &[u8], dst: &mut B) -> usize
where
    B: BufMut + ?Sized,
{
    let src_len = src.len();

    let dst_chunk = dst.chunk_mut();
    if dst_chunk.len() >= src_len {
        let size = url_decode_inner(src, dst_chunk);
        unsafe {
            dst.advance_mut(size);
        }
        return size;
    }

    let mut idx = 0;
    let mut size = 0;

    loop {
        if idx == src_len {
            break;
        }

        let ch = src[idx];
        if ch == b'%' && idx + 2 < src_len {
            let d1 = from_hex_digit(src[idx + 1]);
            let d2 = from_hex_digit(src[idx + 2]);
            if d1.is_none() || d2.is_none() {
                dst.put_slice(&[b'%', d1.unwrap(), d2.unwrap()]);
                size += 3;
                idx += 3;
                continue;
            }

            dst.put_bytes((d1.unwrap() << 4) | d2.unwrap(), 1);
            size += 1;
            idx += 3;
            continue;
        }

        dst.put_bytes(ch, 1);
        idx += 1;
        size += 1;
    }

    size
}

#[cfg(test)]
mod tests {

    use crate::utils::encoding::url_decode;

    use super::url_encode;

    #[test]
    fn test_url_encode() {
        let src = "?%# +abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-=*&^!@~`{}[]><,.;'\"";
        let src1 = "%3F%25%23%20+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-=*&^!@~`{}[]><,.;'\"";
        let src2 = "%3F%25%23%20%2BabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-=*&^!@~`{}[]><,.;'\"";
        let src3 = "%3F%25%23%20%2BabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-%3D*%26%5E!%40~%60%7B%7D%5B%5D%3E%3C%2C.%3B'%22";

        let mut dst = Vec::new();
        let res = url_encode(src.as_bytes(), &mut dst, super::EscapeType::Uri);
        assert_eq!(res, 95);
        assert_eq!(&dst, src1.as_bytes());
        println!("{} {}", res, String::from_utf8_lossy(&dst));

        let mut dst = Vec::new();
        let res = url_encode(src.as_bytes(), &mut dst, super::EscapeType::Args);
        assert_eq!(res, 97);
        assert_eq!(&dst, src2.as_bytes());
        println!("{} {}", res, String::from_utf8_lossy(&dst));

        let mut dst = Vec::new();
        let res = url_encode(
            src.as_bytes(),
            &mut dst,
            super::EscapeType::UriComponent,
        );
        assert_eq!(res, 125);
        assert_eq!(&dst, src3.as_bytes());
        println!("{} {}", res, String::from_utf8_lossy(&dst));

        let mut dst = Vec::with_capacity(src.len() * 3);
        let res = url_encode(src.as_bytes(), &mut dst, super::EscapeType::Uri);
        assert_eq!(res, 95);
        assert_eq!(&dst, src1.as_bytes());
        println!("{} {}", res, String::from_utf8_lossy(&dst));

        let mut dst = Vec::with_capacity(src.len() * 3);
        let res = url_encode(src.as_bytes(), &mut dst, super::EscapeType::Args);
        assert_eq!(res, 97);
        assert_eq!(&dst, src2.as_bytes());
        println!("{} {}", res, String::from_utf8_lossy(&dst));

        let mut dst = Vec::with_capacity(src.len() * 3);
        let res = url_encode(
            src.as_bytes(),
            &mut dst,
            super::EscapeType::UriComponent,
        );
        assert_eq!(res, 125);
        assert_eq!(&dst, src3.as_bytes());
        println!("{} {}", res, String::from_utf8_lossy(&dst));

        // ====================

        let mut dst = Vec::new();
        let res = url_decode(src1.as_bytes(), &mut dst);
        assert_eq!(res, src.len());
        assert_eq!(&dst, src.as_bytes());
        println!("{} {}", res, String::from_utf8_lossy(&dst));

        let mut dst = Vec::with_capacity(src1.len());
        let res = url_decode(src1.as_bytes(), &mut dst);
        assert_eq!(res, src.len());
        assert_eq!(&dst, src.as_bytes());
        println!("{} {}", res, String::from_utf8_lossy(&dst));

        let mut dst = Vec::new();
        let res = url_decode(src2.as_bytes(), &mut dst);
        assert_eq!(res, src.len());
        assert_eq!(&dst, src.as_bytes());
        println!("{} {}", res, String::from_utf8_lossy(&dst));

        let mut dst = Vec::with_capacity(src2.len());
        let res = url_decode(src2.as_bytes(), &mut dst);
        assert_eq!(res, src.len());
        assert_eq!(&dst, src.as_bytes());
        println!("{} {}", res, String::from_utf8_lossy(&dst));

        let mut dst = Vec::new();
        let res = url_decode(src3.as_bytes(), &mut dst);
        assert_eq!(res, src.len());
        assert_eq!(&dst, src.as_bytes());
        println!("{} {}", res, String::from_utf8_lossy(&dst));

        let mut dst = Vec::with_capacity(src3.len());
        let res = url_decode(src3.as_bytes(), &mut dst);
        assert_eq!(res, src.len());
        assert_eq!(&dst, src.as_bytes());
        println!("{} {}", res, String::from_utf8_lossy(&dst));
    }
}
